package net.redxyz.repost.service;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.zhiqim.httpd.HttpHeader;
import org.zhiqim.kernel.Global;
import org.zhiqim.kernel.config.ItemType;
import org.zhiqim.kernel.constants.ZhiqimConstants;
import org.zhiqim.kernel.logging.Log;
import org.zhiqim.kernel.logging.LogFactory;
import org.zhiqim.kernel.schedule.Minute;
import org.zhiqim.kernel.schedule.Scheduler;
import org.zhiqim.kernel.schedule.Task;
import org.zhiqim.kernel.util.Classes;
import org.zhiqim.kernel.util.DateTimes;
import org.zhiqim.kernel.util.codes.MD5;
import org.zhiqim.manager.dao.ZmrParamDao;
import org.zhiqim.orm.ORM;
import org.zhiqim.orm.ZTable;
import org.zhiqim.orm.dbo.Selector;
import org.zhiqim.orm.dbo.Updater;

import com.xiyuan.util.json.Jsons;

import net.redxyz.top.ctrl.TaobaoAcccessParameter;
import net.redxyz.top.ctrl.TaobaoAccessHook;
import net.redxyz.top.dbo.sys.TaobaoApiAccessLog;
import net.redxyz.top.dbo.sys.TaobaoApiAccessStack;
import net.redxyz.top.message.ErrorResponse;
import net.redxyz.top.message.TaobaoRequest;
import net.redxyz.top.message.TaobaoResponse;

public class TaobaoApiHook implements TaobaoAccessHook
{
    private static Log log = LogFactory.getLog(TaobaoApiHook.class);
    
    // 不需要保存日志的API
    private static Map<String, Object> unlogApiMap = new HashMap<String, Object>();
    
    /**
     * 日志清理任务
     * 
     * @author lgz 2021-11-17 新建或整理
     * 
     */
    private static class AccessLogClearTask implements Task
    {
        
        @Override
        public void execute()
        {
            
            try
            {
                ZmrParamDao.addParamIfNotExists("taoao.api.hook", "log_expire_days", "15", 1, ItemType.PUBLIC, null, "淘宝API 调用日志保留天数");
                int days = ZmrParamDao.getInt("taoao.api.hook", "log_expire_days", 15);
                if (days < 1)
                    days = 1;
                
                String day_date = DateTimes.getPreviousDateString(days);
                List<String> list = new ArrayList<String>();
                for (int i = 1; i <= 15; i++)
                    list.add(DateTimes.getPreviousDateString(day_date, i));// 15 天前的前15 天 数据
                
                Selector sel = new Selector();
                
                // 用in 方式， 考虑到大数据量使用 >= <= // 条件不走索引导查询数据过大，从而影响数据库性能
                sel.addMustIn("reqDayDate", list.toArray(new String[0]));
                
                ORM.get(ZTable.class).delete(TaobaoApiAccessStack.class, sel);
                ORM.get(ZTable.class).delete(TaobaoApiAccessLog.class, sel);
            }
            catch (Exception e)
            {
                log.error("清理API调用日志异常：", e);
            }
            
        }
    }
    
    // 定义全局清理任务
    private static Minute log_clear_task = null;
    private boolean isInited = false;
    
    public TaobaoApiHook()
    {
        this.init();// 被创建时初始化
    }
    
    public void init()
    {
        if (isInited)
            return;
        
        unlogApiMap.put("taobao.tmc.messages.consume", "服务消息因为没三秒就会查询一次，不处理会导致很多无用数据增加数据库压力");
        unlogApiMap.put("taobao.tmc.messages.confirm", "服务消息因为没三秒就会查询一次，不处理会导致很多无用数据增加数据库压力");
        
        // 初始化任务
        log_clear_task = new Minute(new AccessLogClearTask(), 25);// 1分钟删除一次
        Global.getWithoutNew(Scheduler.class).addTask(log_clear_task.setImmediateExecuteOnce());
        this.isInited = true;
    }
    
    @Override
    public boolean isOnece()
    {
        
        return false;
    }
    
    @Override
    public void invokeSuccess(TaobaoAcccessParameter<TaobaoRequest<? extends TaobaoResponse>, TaobaoResponse> param) throws Exception
    {
        doSaveAccessLog(param);
    }
    
    @Override
    public void invokeFailure(TaobaoAcccessParameter<TaobaoRequest<? extends TaobaoResponse>, TaobaoResponse> param) throws Exception
    {
        doSaveAccessLog(param);
    }
    
    private void doSaveAccessLog(TaobaoAcccessParameter<TaobaoRequest<? extends TaobaoResponse>, TaobaoResponse> param) throws Exception
    {
        
        // 做记录保存
        TaobaoRequest<? extends TaobaoResponse> req = param.getReq();
        TaobaoResponse rsp = param.getRsp();
        
        if (unlogApiMap.containsKey(req.getMethod()))
        {// 不需要记录的API
            return;
        }
        
        StackTraceElement[] stes = param.getStack();
        StringBuilder strb = new StringBuilder();
        for (int i = 1; i < stes.length; i++)
        {// 这里从1开始 因为 0 是从TaobaoAcccessParameter 里面
            StackTraceElement s = stes[i];
            if (strb.length() > 0)
                strb.append("\r\n");
            
            strb.append(s.toString());
        }
        
        int invockType = 0; // 1， http调用，2=task调用 ，0=其他地方调用
        for (int i = stes.length - 1; i > 0; i--)
        {
            StackTraceElement s = stes[i];
            Class<?> clz = Classes.forName(s.getClassName());
            if (clz != null)
            {
                if (HttpHeader.class.isAssignableFrom(clz))
                {
                    invockType = 1;// http
                    break;
                }
                else if (Task.class.isAssignableFrom(clz))
                {
                    invockType = 2;// task
                    break;
                }
            }
        }
        
        // 保存栈信息链路
        String processName = Global.getString(ZhiqimConstants.Z_BOOT, "process");
        if (processName == null)
            processName = Global.getName();
        String processIp = "localhost";
        try
        {
            InetAddress ip4 = Inet4Address.getLocalHost();
            processIp = ip4.getHostAddress();
        }
        catch (UnknownHostException e)
        {
            e.printStackTrace();
        }
        
        String day_date = DateTimes.getDateString();// 调用日期
        int len = strb.length();
        strb.append(req.getMethod());// 添加 调用方法
        strb.append(day_date);// 添加调用日期
        strb.append(processName);// 加上程序名称
        strb.append(processIp);// 加上程序IP
        
        String invockStackId = MD5.encodeUTF8(strb.toString());// 构建ID
        strb.setLength(len);// 还原 堆栈长度
        
        boolean isSuccess = rsp != null && rsp.isSuccess();
        
        // 创建 API 调用链路
        TaobaoApiAccessStack stack = new TaobaoApiAccessStack();
        stack.setInvockStackId(invockStackId);
        stack.setMethodName(req.getMethod());
        stack.setProcessName(processName);//
        stack.setProcessIp(processIp);//
        stack.setInvockType(invockType);// 调用 折 task, 还是 tttp
        stack.setReqDayDate(day_date);
        stack.setSuccNum(isSuccess ? 1 : 0);
        stack.setFailNum(!isSuccess ? 1 : 0);
        stack.setStackInfo(strb.toString());
        strb = null;// 释放内存
        
        int count = ORM.get(ZTable.class).count(TaobaoApiAccessStack.class, new Selector("invockStackId", invockStackId));
        
        // 创建详细日志信息
        ErrorResponse err = rsp != null && !rsp.isSuccess() ? rsp.getError() : new ErrorResponse();
        
        String submsg = err.getSubMsg();
        if (submsg != null && submsg.length() > 5000)
            submsg = submsg.substring(0, 5000);
        
        String reqs = Jsons.toString(req.getParameterMap());
        
        TaobaoApiAccessLog apiLog = new TaobaoApiAccessLog();
        
        apiLog.setCode(err.getCode());
        apiLog.setErrorMsg(err.getErrorMsg());
        apiLog.setSubCode(err.getSubCode());
        apiLog.setSubMessage(submsg);
        apiLog.setUserNick(req.getNick());
        
        apiLog.setInvockStackId(invockStackId);
        apiLog.setMethodName(req.getMethod());
        apiLog.setSuccess(isSuccess);
        apiLog.setReqDayDate(day_date);
        apiLog.setCreateTime(DateTimes.getDateTimeString());
        
        apiLog.setReqTime(req.getReqTime());
        apiLog.setRspTime(rsp != null ? rsp.getRspTime() : null);
        apiLog.setRequestId(rsp != null ? rsp.getRequestId() : null);
        apiLog.setRequestInfo(reqs);
        
        ORM.get(ZTable.class).insert(apiLog);// 插入日志
        
        // 日志计数
        Updater upd = new Updater();
        upd.addMust("invockStackId", invockStackId);
        if (isSuccess)
            upd.addFieldExpression("succNum", "succ_num+1");
        else
            upd.addFieldExpression("failNum", "fail_num+1");
        
        if (count > 0)
            ORM.get(ZTable.class).update(TaobaoApiAccessStack.class, upd);
        else
            ORM.get(ZTable.class).insert(stack);
    }
    
}
